﻿using System;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Jinndev.Modding;

namespace Jinndev.Tile2D {

    /// <summary>
    /// 包含一个二位Tile数组和二位Entity数组的Tile层级<br/>
    /// 同一个Entity可以对应多个Tile，但其中只有一个是主Tile，其他的作为填充Tile<br/>
    /// 指定一个TileLayerHandler用来实现具体的Entity操作
    /// </summary>
    public class TileLayer : IDimension2D, IDisposable {

        public string Name { get; private set; }
        public int2 Dimension { get; private set; }
        public TileLayerHandler Handler { get; private set; }

        public Tile[,] tiles;
        public Entity[,] entities;

        public TileLayer(string name, int2 dimension, TileLayerHandler handler) {
            if(dimension.x <= 0 || dimension.y <= 0) {
                throw new Exception($"Invalid dimension {dimension}");
            }
            if(handler == null) {
                throw new Exception("TileLayerHandler is null");
            }

            Name = name;
            Dimension = dimension;
            Handler = handler;

            tiles = new Tile[Dimension.x, Dimension.y];
            entities = new Entity[Dimension.x, Dimension.y];
            for (int x = 0; x < Dimension.x; x++) {
                for (int y = 0; y < Dimension.y; y++) {
                    tiles[x, y] = Tile.Empty;
                    entities[x, y] = Entity.Null;
                }
            }
        }

        /// <summary>
        /// 销毁Tile和Entity
        /// </summary>
        public void Dispose() {
            if (CommonUtil.IsQuitting) {
                return;
            }

            // 是否取消默认销毁Entity的方法，取消意味已经着由Handler进行了销毁方法
            bool cancelDefault = Handler.DestroyAllEntities();
            if (!cancelDefault) {
                // 默认销毁Entity的方法
                for (int x = 0; x < Dimension.x; x++) {
                    for (int y = 0; y < Dimension.y; y++) {
                        DestroyTiles(new int2(x, y));
                    }
                }
            }
            // 如果进行了默认销毁，则这里应该已经全部是空了
            for (int x = 0; x < Dimension.x; x++) {
                for (int y = 0; y < Dimension.y; y++) {
                    tiles[x, y] = Tile.Empty;
                    entities[x, y] = Entity.Null;
                }
            }
        }

        /// <summary>
        /// 以startIndex为起始点，按照direction设置属于renderConfig的所有关联Tile，返回起始点的主Tile
        /// </summary>
        public Tile SetTile(int2 startIndex, RenderConfig renderConfig, int direction) {
            int2 dimension = renderConfig.GetRealDimension(direction);
            if(dimension.x <= 0 || dimension.y <= 0) {
                Debug.LogError($"Invalid dimension: {dimension}, sid: {renderConfig.Sid}, direction: {direction}");
                return Tile.Empty;
            }
            // 检查是否超出范围
            if (!IsInRange(startIndex, dimension)) {
                Debug.LogError($"Tile out of range, start: {startIndex}, dimension: {dimension}");
                return Tile.Empty;
            }

            if(GetMainTile(startIndex, out Tile mainTile) && GetEntity(startIndex, out Entity entity)) {
                // 检测替换条件
                if(Handler.CanReplaceTile(this, renderConfig, mainTile)) {
                    // 可以替换
                    return ReplaceTile(mainTile, entity, startIndex, renderConfig, direction);
                }
                else {
                    // 否则先移除被覆盖的Tile和Entity
                    DestroyTiles(startIndex);
                }
            }

            // 创建新的Tile和Entity
            return CreateTiles(Guid.NewGuid(), startIndex, renderConfig, direction);
        }

        /// <summary>
        /// 通过Tiles数组直接初始化并创建Enitty
        /// </summary>
        /// <param name="tiles"></param>
        public void InitTiles(Tile[,] tiles) {
            foreach(Tile tile in tiles) {
                if (tile.IsMain()) {
                    // 检查是否超出范围
                    if (!IsInRange(tile.startIndex, tile.realDimension)) {
                        Debug.LogError($"Tile out of range, start: {tile.startIndex}, dimension: {tile.realDimension}");
                        continue;
                    }
                    // 根据新Tile，清理出存储区域
                    tile.Foreach(DestroyTiles);
                    // 创建Tile
                    RenderConfig renderConfig = ModManager.GetSubConfig<RenderConfig>(tile.cfgId);
                    CreateTiles(tile.guid, tile.startIndex, renderConfig, tile.direction);
                }
            }
        }

        /// <summary>
        /// 替换
        /// </summary>
        private Tile ReplaceTile(Tile mainTile, Entity entity, int2 startIndex, RenderConfig renderConfig, int direction) {
            int2 dimension = renderConfig.GetRealDimension(direction);
            // 检查新的范围内，是否有其他Tile
            if(!HasOtherTile(startIndex, dimension, mainTile)) {
                // 不能替换
                return Tile.Empty;
            }
            // 清空原范围数据
            mainTile.Foreach((x, y)=> {
                entities[x, y] = Entity.Null;
                tiles[x, y] = Tile.Empty;
            });
            // 设置新范围数据
            mainTile.startIndex = startIndex;
            mainTile.direction = direction;
            mainTile.Setup(renderConfig);

            mainTile.Foreach((x, y) => {
                entities[x, y] = entity;
                int2 index = new int2(x, y);
                if(index.Equals(mainTile.startIndex)) {
                    tiles[x, y] = mainTile;
                }
                else {
                    tiles[x, y] = Tile.CreatePadding(index, direction, renderConfig, startIndex);
                }
            });

            UpdateTileEntity(mainTile, entity, renderConfig);
            return mainTile;
        }

        /// <summary>
        /// 范围内，是否有除了targetTile以外的其他Tile
        /// </summary>
        private bool HasOtherTile(int2 startIndex, int2 dimension, Tile targetTile) {
            for (int y = startIndex.y; y < startIndex.y + dimension.y; y++) {
                for (int x = startIndex.x; x < startIndex.x + dimension.x; x++) {
                    if (IsInRange(x, y)) {
                        Tile tile = tiles[x, y];
                        if (!tile.IsEmpty() && tile.startIndex != targetTile.startIndex) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// 范围内是否是空的，且在范围内
        /// </summary>
        public bool IsEmpty(int2 startIndex, int2 dimension) {
            for (int y = startIndex.y; y < startIndex.y + dimension.y; y++) {
                for (int x = startIndex.x; x < startIndex.x + dimension.x; x++) {
                    if(!IsInRange(x, y) || !tiles[x, y].IsEmpty()) {
                        return false;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// 更新Tile
        /// </summary>
        private void UpdateTileEntity(Tile mainTile, Entity entity, RenderConfig renderConfig) {
            try {
                Handler.EntityManager.SetComponentData(entity, mainTile);
                Handler.UpdateTile(this, tiles, mainTile, entity, renderConfig);

                if (Handler.NeedUpdateNeighbours(this, mainTile, entity, renderConfig)) {
                    // 找到四周的邻居mainTile
                    HashSet<Tile> neighbours = GetNeighbours(mainTile);
                    // 更新邻居
                    foreach (Tile nMainTile in neighbours) {
                        int2 index = nMainTile.startIndex;
                        Entity nEntity = entities[index.x, index.y];
                        RenderConfig nRenderCofnig = ModManager.GetSubConfig<RenderConfig>(nMainTile.cfgId);
                        Handler.EntityManager.SetComponentData(nEntity, nMainTile);
                        Handler.UpdateTile(this, tiles, nMainTile, nEntity, nRenderCofnig);
                    }
                }

                Handler.AfterUpdateTile(this, tiles, mainTile, entity, renderConfig);
            }
            catch (Exception e) {
                Debug.LogException(e);
            }
        }
        

        /// <summary>
        /// 以startIndex为起始点，按照direction创建属于renderConfig的所有关联Tile，返回起始点的主Tile
        /// </summary>
        private Tile CreateTiles(Guid guid, int2 startIndex, RenderConfig renderConfig, int direction) {
            // 创建Entity
            Entity entity = Handler.CreateTileEntity(this, guid, startIndex, renderConfig);
            if (entity == Entity.Null) {
                Debug.LogError($"Can't create tile entity, sid: {renderConfig.Sid}");
                return Tile.Empty;
            }
            // 创建Tile
            Tile mainTile = TileUtil.CreateTile(tiles, guid, startIndex, renderConfig, direction);
            if(mainTile == Tile.Empty) {
                Debug.LogError($"Can't create tile, sid: {renderConfig.Sid}");
                return Tile.Empty;
            }
            // 保存Entity
            int2 dimension = renderConfig.GetRealDimension(direction);
            TileUtil.Foreach(startIndex, dimension, (x, y) => { 
                entities[x, y] = entity;
            });
            // 根据Tile更新Entity
            UpdateTileEntity(mainTile, entity, renderConfig);
            return mainTile;
        }

        /// <summary>
        /// 销毁index处的实体和所有关联Tile
        /// </summary>
        public void DestroyTiles(int2 index) {
            if (GetMainTile(index, out Tile mainTile)) {
                Entity entity = entities[index.x, index.y];
                try {
                    if(!Handler.BeforeDestroyTile(this, mainTile, entity)) {
                        DestroyEntity(entity);
                    }
                }
                catch (Exception e) {
                    Debug.LogException(e);
                }
                
                mainTile.Foreach((x, y) => {
                    tiles[x, y] = Tile.Empty;
                    entities[x, y] = Entity.Null;
                });
            }
        }

        private void DestroyEntity(Entity entity) {
            Handler.EntityManager.DestroyEntity(entity);
        }

        public bool GetMainTile(int2 index, out Tile mainTile) {
            if (!IsInRange(index)) {
                mainTile = Tile.Empty;
                return false;
            }
            Tile tile = tiles[index.x, index.y];
            return GetMainTile(tile, out mainTile);
        }

        public bool GetMainTile(Tile tile, out Tile mainTile) {
            if (tile.IsMain()) {
                mainTile = tile;
                return true;
            }
            if (tile.IsPadding()) {
                int2 startIndex = tile.startIndex;
                mainTile = tiles[startIndex.x, startIndex.y];
                return mainTile.IsMain();
            }
            mainTile = Tile.Empty;
            return false;
        }

        public bool GetEntity(Tile tile, out Entity entity) {
            if (GetMainTile(tile, out Tile mainTile)) {
                entity = entities[mainTile.startIndex.x, mainTile.startIndex.y];
                return entity != Entity.Null;
            }
            entity = Entity.Null;
            return false;
        }

        public bool GetEntity(int2 index, out Entity entity) {
            if (GetMainTile(index, out Tile mainTile)) {
                entity = entities[mainTile.startIndex.x, mainTile.startIndex.y];
                return entity != Entity.Null;
            }
            entity = Entity.Null;
            return false;
        }

        public bool Get(int2 index, out Tile mainTile, out Entity entity) {
            if(GetMainTile(index, out mainTile)) {
                entity = entities[mainTile.startIndex.x, mainTile.startIndex.y];
                return entity != Entity.Null;
            }
            entity = Entity.Null;
            return false;
        }

        /// <summary>
        /// 根据mainTile大小，获得周围的邻居
        /// </summary>
        public HashSet<Tile> GetNeighbours(Tile mainTile) {
            // 找到四周的邻居index
            HashSet<int2> neighboursIndex = GetNeighboursIndex(mainTile);
            // 找到四周的邻居mainTile
            HashSet<Tile> neighbours = new HashSet<Tile>();
            foreach (int2 index in neighboursIndex) {
                if (GetMainTile(index, out Tile nMainTile)) {
                    neighbours.Add(nMainTile);
                }
            }
            return neighbours;
        }

        /// <summary>
        /// 根据mainTile大小，获得周围相邻的索引
        /// </summary>
        public HashSet<int2> GetNeighboursIndex(Tile mainTile) {
            int2 startIndex = mainTile.startIndex;
            int2 dimension = mainTile.realDimension;
            // 找到四周的邻居index
            HashSet<int2> neighbours = new HashSet<int2>();
            int2 index;
            for (int y = 0; y < dimension.y; y++) {
                index = new int2(startIndex.x - 1, startIndex.y + y);
                if (IsInRange(index)) {
                    neighbours.Add(index);
                }

                index = new int2(startIndex.x + dimension.x, startIndex.y + y);
                if (IsInRange(index)) {
                    neighbours.Add(index);
                }
            }
            for (int x = 0; x < dimension.x; x++) {
                index = new int2(startIndex.x + x, startIndex.y - 1);
                if (IsInRange(index)) {
                    neighbours.Add(index);
                }

                index = new int2(startIndex.x + x, startIndex.y + dimension.y);
                if (IsInRange(index)) {
                    neighbours.Add(index);
                }
            }
            return neighbours;
        }

        public bool IsInRange(int x, int y) {
            return x >= 0 && y >= 0 && x < Dimension.x && y < Dimension.y;
        }

        public bool IsInRange(int2 index) {
            return IsInRange(index.x, index.y);
        }

        public bool IsInRange(int2 startIndex, int2 dimension) {
            return startIndex.x >= 0 && (startIndex.x + dimension.x) <= Dimension.x
                && startIndex.y >= 0 && (startIndex.y + dimension.y) <= Dimension.y;
        }

        public void Foreach(int2 startIndex, int2 dimension, Action<int2> action) {
            TileUtil.Foreach(startIndex, dimension, (index) => {
                if (IsInRange(index)) {
                    action.Invoke(index);
                }
            });
        }

        public void Foreach(int2 startIndex, int2 dimension, Action<int, int> action) {
            TileUtil.Foreach(startIndex, dimension, (x, y) => {
                if (IsInRange(x, y)) {
                    action.Invoke(x, y);
                }
            });
        }

    }

    

}
